1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.google.common.util.concurrent;
18
19 import com.google.common.testing.TearDown;
20 import com.google.common.testing.TearDownStack;
21
22 import junit.framework.TestCase;
23
24 import java.lang.Thread.UncaughtExceptionHandler;
25 import java.util.concurrent.CountDownLatch;
26 import java.util.concurrent.Executor;
27 import java.util.concurrent.ExecutorService;
28 import java.util.concurrent.Executors;
29 import java.util.concurrent.TimeUnit;
30 import java.util.concurrent.TimeoutException;
31
32
33
34
35
36
37 public class AbstractExecutionThreadServiceTest extends TestCase {
38
39 private final TearDownStack tearDownStack = new TearDownStack(true);
40 private final CountDownLatch enterRun = new CountDownLatch(1);
41 private final CountDownLatch exitRun = new CountDownLatch(1);
42
43 private Thread executionThread;
44 private Throwable thrownByExecutionThread;
45 private final Executor exceptionCatchingExecutor = new Executor() {
46 @Override
47 public void execute(Runnable command) {
48 executionThread = new Thread(command);
49 executionThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
50 @Override
51 public void uncaughtException(Thread thread, Throwable e) {
52 thrownByExecutionThread = e;
53 }
54 });
55 executionThread.start();
56 }
57 };
58
59 @Override protected final void tearDown() {
60 tearDownStack.runTearDown();
61 }
62
63 public void testServiceStartStop() throws Exception {
64 WaitOnRunService service = new WaitOnRunService();
65 assertFalse(service.startUpCalled);
66
67 service.startAsync().awaitRunning();
68 assertTrue(service.startUpCalled);
69 assertEquals(Service.State.RUNNING, service.state());
70
71 enterRun.await();
72
73 service.stopAsync().awaitTerminated();
74 assertTrue(service.shutDownCalled);
75 assertEquals(Service.State.TERMINATED, service.state());
76 executionThread.join();
77 assertNull(thrownByExecutionThread);
78 }
79
80 public void testServiceStopIdempotence() throws Exception {
81 WaitOnRunService service = new WaitOnRunService();
82
83 service.startAsync().awaitRunning();
84 enterRun.await();
85
86 service.stopAsync();
87 service.stopAsync();
88 service.stopAsync().awaitTerminated();
89 assertEquals(Service.State.TERMINATED, service.state());
90 service.stopAsync().awaitTerminated();
91 assertEquals(Service.State.TERMINATED, service.state());
92
93 executionThread.join();
94 assertNull(thrownByExecutionThread);
95 }
96
97 public void testServiceExitingOnItsOwn() throws Exception {
98 WaitOnRunService service = new WaitOnRunService();
99 service.expectedShutdownState = Service.State.RUNNING;
100
101 service.startAsync().awaitRunning();
102 assertTrue(service.startUpCalled);
103 assertEquals(Service.State.RUNNING, service.state());
104
105 exitRun.countDown();
106 executionThread.join();
107
108 assertTrue(service.shutDownCalled);
109 assertEquals(Service.State.TERMINATED, service.state());
110 assertNull(thrownByExecutionThread);
111
112 service.stopAsync().awaitTerminated();
113 assertEquals(Service.State.TERMINATED, service.state());
114 assertTrue(service.shutDownCalled);
115 }
116
117 private class WaitOnRunService extends AbstractExecutionThreadService {
118 private boolean startUpCalled = false;
119 private boolean runCalled = false;
120 private boolean shutDownCalled = false;
121 private State expectedShutdownState = State.STOPPING;
122
123 @Override protected void startUp() {
124 assertFalse(startUpCalled);
125 assertFalse(runCalled);
126 assertFalse(shutDownCalled);
127 startUpCalled = true;
128 assertEquals(State.STARTING, state());
129 }
130
131 @Override protected void run() {
132 assertTrue(startUpCalled);
133 assertFalse(runCalled);
134 assertFalse(shutDownCalled);
135 runCalled = true;
136 assertEquals(State.RUNNING, state());
137
138 enterRun.countDown();
139 try {
140 exitRun.await();
141 } catch (InterruptedException e) {
142 throw new RuntimeException(e);
143 }
144 }
145
146 @Override protected void shutDown() {
147 assertTrue(startUpCalled);
148 assertTrue(runCalled);
149 assertFalse(shutDownCalled);
150 shutDownCalled = true;
151 assertEquals(expectedShutdownState, state());
152 }
153
154 @Override protected void triggerShutdown() {
155 exitRun.countDown();
156 }
157
158 @Override protected Executor executor() {
159 return exceptionCatchingExecutor;
160 }
161 }
162
163 public void testServiceThrowOnStartUp() throws Exception {
164 ThrowOnStartUpService service = new ThrowOnStartUpService();
165 assertFalse(service.startUpCalled);
166
167 service.startAsync();
168 try {
169 service.awaitRunning();
170 fail();
171 } catch (IllegalStateException expected) {
172 assertEquals("kaboom!", expected.getCause().getMessage());
173 }
174 executionThread.join();
175
176 assertTrue(service.startUpCalled);
177 assertEquals(Service.State.FAILED, service.state());
178 assertTrue(thrownByExecutionThread.getMessage().equals("kaboom!"));
179 }
180
181 private class ThrowOnStartUpService extends AbstractExecutionThreadService {
182 private boolean startUpCalled = false;
183
184 @Override protected void startUp() {
185 startUpCalled = true;
186 throw new UnsupportedOperationException("kaboom!");
187 }
188
189 @Override protected void run() {
190 throw new AssertionError("run() should not be called");
191 }
192
193 @Override protected Executor executor() {
194 return exceptionCatchingExecutor;
195 }
196 }
197
198 public void testServiceThrowOnRun() throws Exception {
199 ThrowOnRunService service = new ThrowOnRunService();
200
201 service.startAsync();
202 try {
203 service.awaitTerminated();
204 fail();
205 } catch (IllegalStateException expected) {
206 executionThread.join();
207 assertEquals(thrownByExecutionThread, expected.getCause());
208 }
209 assertTrue(service.shutDownCalled);
210 assertEquals(Service.State.FAILED, service.state());
211 assertEquals("kaboom!", thrownByExecutionThread.getMessage());
212 }
213
214 public void testServiceThrowOnRunAndThenAgainOnShutDown() throws Exception {
215 ThrowOnRunService service = new ThrowOnRunService();
216 service.throwOnShutDown = true;
217
218 service.startAsync();
219 try {
220 service.awaitTerminated();
221 fail();
222 } catch (IllegalStateException expected) {
223 executionThread.join();
224 assertEquals(thrownByExecutionThread, expected.getCause());
225 }
226
227 assertTrue(service.shutDownCalled);
228 assertEquals(Service.State.FAILED, service.state());
229 assertEquals("kaboom!", thrownByExecutionThread.getMessage());
230 }
231
232 private class ThrowOnRunService extends AbstractExecutionThreadService {
233 private boolean shutDownCalled = false;
234 private boolean throwOnShutDown = false;
235
236 @Override protected void run() {
237 throw new UnsupportedOperationException("kaboom!");
238 }
239
240 @Override protected void shutDown() {
241 shutDownCalled = true;
242 if (throwOnShutDown) {
243 throw new UnsupportedOperationException("double kaboom!");
244 }
245 }
246
247 @Override protected Executor executor() {
248 return exceptionCatchingExecutor;
249 }
250 }
251
252 public void testServiceThrowOnShutDown() throws Exception {
253 ThrowOnShutDown service = new ThrowOnShutDown();
254
255 service.startAsync().awaitRunning();
256 assertEquals(Service.State.RUNNING, service.state());
257
258 service.stopAsync();
259 enterRun.countDown();
260 executionThread.join();
261
262 assertEquals(Service.State.FAILED, service.state());
263 assertEquals("kaboom!", thrownByExecutionThread.getMessage());
264 }
265
266 private class ThrowOnShutDown extends AbstractExecutionThreadService {
267 @Override protected void run() {
268 try {
269 enterRun.await();
270 } catch (InterruptedException e) {
271 throw new RuntimeException(e);
272 }
273 }
274
275 @Override protected void shutDown() {
276 throw new UnsupportedOperationException("kaboom!");
277 }
278
279 @Override protected Executor executor() {
280 return exceptionCatchingExecutor;
281 }
282 }
283
284 public void testServiceTimeoutOnStartUp() throws Exception {
285 TimeoutOnStartUp service = new TimeoutOnStartUp();
286
287 try {
288 service.startAsync().awaitRunning(1, TimeUnit.MILLISECONDS);
289 fail();
290 } catch (TimeoutException e) {
291 assertTrue(e.getMessage().contains(Service.State.STARTING.toString()));
292 }
293 }
294
295 private class TimeoutOnStartUp extends AbstractExecutionThreadService {
296 @Override protected Executor executor() {
297 return new Executor() {
298 @Override public void execute(Runnable command) {
299 }
300 };
301 }
302
303 @Override
304 protected void run() throws Exception {
305 }
306 }
307
308 public void testStopWhileStarting_runNotCalled() throws Exception {
309 final CountDownLatch started = new CountDownLatch(1);
310 FakeService service = new FakeService() {
311 @Override protected void startUp() throws Exception {
312 super.startUp();
313 started.await();
314 }
315 };
316 service.startAsync();
317 service.stopAsync();
318 started.countDown();
319 service.awaitTerminated();
320 assertEquals(Service.State.TERMINATED, service.state());
321 assertEquals(1, service.startupCalled);
322 assertEquals(0, service.runCalled);
323 assertEquals(1, service.shutdownCalled);
324 }
325
326 public void testStop_noStart() {
327 FakeService service = new FakeService();
328 service.stopAsync().awaitTerminated();
329 assertEquals(Service.State.TERMINATED, service.state());
330 assertEquals(0, service.startupCalled);
331 assertEquals(0, service.runCalled);
332 assertEquals(0, service.shutdownCalled);
333 }
334
335 public void testDefaultService() throws InterruptedException {
336 WaitOnRunService service = new WaitOnRunService();
337 service.startAsync().awaitRunning();
338 enterRun.await();
339 service.stopAsync().awaitTerminated();
340 }
341
342 private class FakeService extends AbstractExecutionThreadService implements TearDown {
343
344 private final ExecutorService executor = Executors.newSingleThreadExecutor();
345
346 FakeService() {
347 tearDownStack.addTearDown(this);
348 }
349
350 volatile int startupCalled = 0;
351 volatile int shutdownCalled = 0;
352 volatile int runCalled = 0;
353
354 @Override protected void startUp() throws Exception {
355 assertEquals(0, startupCalled);
356 assertEquals(0, runCalled);
357 assertEquals(0, shutdownCalled);
358 startupCalled++;
359 }
360
361 @Override protected void run() throws Exception {
362 assertEquals(1, startupCalled);
363 assertEquals(0, runCalled);
364 assertEquals(0, shutdownCalled);
365 runCalled++;
366 }
367
368 @Override protected void shutDown() throws Exception {
369 assertEquals(1, startupCalled);
370 assertEquals(0, shutdownCalled);
371 assertEquals(Service.State.STOPPING, state());
372 shutdownCalled++;
373 }
374
375 @Override protected Executor executor() {
376 return executor;
377 }
378
379 @Override public void tearDown() throws Exception {
380 executor.shutdown();
381 }
382 }
383 }